﻿using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Utils.Exceptions.Universes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;

namespace Sustainalytics.DataLayer
{
    public class CompaniesRepository : ICompaniesRepository
    {
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;

        public CompaniesRepository(IMongoCollection<CorporateData> corporateDataCollection)
        {
            _corporateDataCollection = corporateDataCollection;
        }
        static CompaniesRepository()
        {
        }

       public CompaniesRepository(string connectionString)
        {
            _corporateDataCollection = MongoFactory.GetCollectionFromDatabase<CorporateData>(connectionString);

            if (string.IsNullOrWhiteSpace(connectionString))
                throw new ArgumentException("No database specified in the connection string!");

            if (_corporateDataCollection == null)
                throw new ArgumentException($"Cannot find CorporateData collection for connection string: {connectionString}!");
        }

        public static CompaniesRepository GetCompanySearchRepository(string connectionString)
        {
            return new CompaniesRepository(connectionString);
        }

        public CorporateData GetCompanyById(Guid id,
            params Expression<Func<CorporateData, object>>[] parExpression)
        {
            ProjectionDefinition<CorporateData> projection = null;
            
            foreach (var columnFunc in parExpression)
            {
                projection = projection == null ? Builders<CorporateData>.Projection.Include(columnFunc) : projection.Include(columnFunc);
            }

            return _corporateDataCollection.Find(x => x.Id == id).Project<CorporateData>(projection).FirstOrDefault();
        }

        public List<CorporateData> GetCompaniesById(List<Guid> ids)
        {
            ProjectionDefinition<CorporateData> projection = null;

            projection = Builders<CorporateData>.Projection.Include(t => t.Id);
            projection = projection.Include(t => t.CompanyName);
            projection = projection.Include(t => t.CompanyNameLowercase);
            projection = projection.Include(t => t.CompanyNameUrlFriendly);
            projection = projection.Include(t => t.Country);
            projection = projection.Include(t => t.PeerGroup);
            projection = projection.Include(t => t.Address);
            projection = projection.Include(t => t.TotalEmployees);
            projection = projection.Include(t => t.CompanyPhone);
            projection = projection.Include(t => t.Website);
            projection = projection.Include(t => t.BusinessDescription);
            projection = projection.Include(t => t.MainTicker);
            projection = projection.Include(t => t.MainExchange);
            projection = projection.Include(t => t.UniverseIds);
            projection = projection.Include(t => t.ResearchParentId);

            return _corporateDataCollection.Find(x => ids.Contains(x.Id)).Project<CorporateData>(projection).ToList();
        }

        public int GetCompanyCount(CompaniesListFilters filters)
        {
            var query = _corporateDataCollection.AsQueryable(new AggregateOptions() { AllowDiskUse = true });

            query = AddFiltersToFindQuery(filters, query);

            return query.Count(a => a.ResearchParentId == null);
        }

        public List<CorporateData> GetResearchGrid(CompaniesListFilters filters, int skip = 0, int take = 100, bool orderAscending = true, string orderBy = "CompanyNameLowercase")
        {
            var query = _corporateDataCollection.AsQueryable(new AggregateOptions() { AllowDiskUse = true });

            if (filters != null)
                query = AddFiltersToFindQuery(filters, query);

            query = AddOrderByToFindQuery(query, orderAscending, orderBy);
            
            return query.Where(a => a.ResearchParentId == null).Skip(skip).Take(take).ToList();
        }

        private IMongoQueryable<CorporateData> AddOrderByToFindQuery(IMongoQueryable<CorporateData> query, bool orderAscending, string orderBy)
        {
            ResearchSortOrderDictionary.TryGetValue(orderBy, out Expression<Func<CorporateData, object>> order);

            if (orderBy.Contains("CompanyNameLowercase"))
            {
                query = orderAscending ? query.OrderBy(order) : query.OrderByDescending(order);
            }
            else
            {
                if (orderBy == "HasEsgResearch")
                    query = orderAscending ? query
                        .OrderBy(x=> x.ComprehensiveEsgResearchState.HasResearch)
                        .ThenBy(x=> x.CoreEsgResearchState.HasResearch)
                        .ThenBy(x => x.CompanyNameLowercase) 
                        : query
                        .OrderByDescending(x => x.ComprehensiveEsgResearchState.HasResearch)
                        .ThenByDescending(x => x.CoreEsgResearchState.HasResearch)
                        .ThenBy(x => x.CompanyNameLowercase);
                else 
                    query = orderAscending ? query.OrderBy(order).ThenBy(x => x.CompanyNameLowercase) : query.OrderByDescending(order).ThenBy(x => x.CompanyNameLowercase);
            }

            return query;
        }

        private static readonly Dictionary<string, Expression<Func<CorporateData, object>>> ResearchSortOrderDictionary = new Dictionary<string, Expression<Func<CorporateData, object>>>()
        {
            { "CompanyNameLowercase",   x => x.CompanyNameLowercase                         },
            { "MainExchange",           x => x.MainExchange                                 },
            { "MainTicker",             x => x.MainTicker                                   },
            { "SubPeerGroup",           x => x.SubPeerGroup                                 },
            { "HasEsgResearch",         x => x.ComprehensiveEsgResearchState.HasResearch    },
            { "HasRiskRatingResearch",  x => x.RiskRatingResearchState.HasResearch          },
            { "HasGovernanceResearch",  x => x.CorporateGovernanceResearchState.HasResearch },
            { "HasControversyResearch", x => x.ControversyResearchState.HasResearch         },
            { "HasGccsResearch",        x => x.GccsResearchState.HasResearch                },
            { "PIResearchState",        x => x.ProductInvolvementResearchState.HasResearch  },
        };


        public CorporateData GetCorporateDataById(Guid companyId)
        {
            var company = _corporateDataCollection.Find(x => x.Id == companyId).SingleOrDefault();

            if (company == null)
                throw new CompanyNotFoundException(string.Format("Company with id: {0} cannot be found.", companyId), "");

            return company;
        }

        private static IMongoQueryable<CorporateData> AddFiltersToFindQuery(CompaniesListFilters filters, IMongoQueryable<CorporateData> query)
        {
            if (!string.IsNullOrEmpty(filters.CompanyNameLowerCase))
            {
                query = query.Where(x => x.CompanyNameLowercase.Contains(filters.CompanyNameLowerCase.ToLowerInvariant()));
            }

            if (!string.IsNullOrEmpty(filters.Industry))
            {
                query = query.Where(x => x.PeerGroup.ToLowerInvariant().Contains(filters.Industry.ToLowerInvariant()));
            }

            if (filters.hasEsgResearch.HasValue)
            {
                if (filters.hasEsgResearch.Value)
                    query = query.Where(x => x.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchAvailable
                                || x.CoreEsgResearchState.ResearchState == ResearchState.ResearchAvailable);
                else
                    query = query.Where(x => x.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchNotAvailable
                                && x.CoreEsgResearchState.ResearchState == ResearchState.ResearchNotAvailable);
            }

            if (filters.hasGovernanceResearch.HasValue)
            {
                var researchState = filters.hasGovernanceResearch.Value ? ResearchState.ResearchAvailable : ResearchState.ResearchNotAvailable;
                query = query.Where(x => x.CorporateGovernanceResearchState.ResearchState == researchState);                
            }

            if (filters.hasControversyResearch.HasValue)
            {
                var researchState = filters.hasControversyResearch.Value ? ResearchState.ResearchAvailable : ResearchState.ResearchNotAvailable;
                query = query.Where(x => x.ControversyResearchState.ResearchState == researchState);
            }

            if (filters.hasGccsResearch.HasValue)
            {
                var researchState = filters.hasGccsResearch.Value ? ResearchState.ResearchAvailable : ResearchState.ResearchNotAvailable;
                query = query.Where(x => x.GccsResearchState.ResearchState == researchState);
            }

            if (filters.hasPIResearch.HasValue)
            {
                var researchState = filters.hasPIResearch.Value ? ResearchState.ResearchAvailable : ResearchState.ResearchNotAvailable;
                query = query.Where(x => x.ProductInvolvementResearchState.ResearchState == researchState);
            }

            if(filters.hasRiskResearch.HasValue)
            {
                var researchState = filters.hasRiskResearch.Value ? ResearchState.ResearchAvailable : ResearchState.ResearchNotAvailable;
                query = query.Where(x => x.RiskRatingResearchState.ResearchState == researchState);
            }

            return query;
        }
    }
}
